package grid.puzzle;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Random;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;

/**
 * The Grid Adapter
 * 
 * @author ciprianursu
 * 
 */
public class GridAdapter extends BaseAdapter {
	public static final int NO_DIRECTION = -1;
	public static final int UP_DIRECTION = 0;
	public static final int DOWN_DIRECTION = 1;
	public static final int LEFT_DIRECTION = 2;
	public static final int RIGHT_DIRECTION = 3;

	public static final int[] mIds = { R.drawable.i1, R.drawable.i2,
			R.drawable.i3, R.drawable.i4, R.drawable.i5, R.drawable.i6,
			R.drawable.i7, R.drawable.i8, R.drawable.i9, R.drawable.i10,
			R.drawable.i11, R.drawable.i12, R.drawable.i13, R.drawable.i14,
			R.drawable.i15 };

	private GridItem[] items;
	private int gridSize;
	private Boolean shuffleOn = false;

	private Activity mActivity;

	/**
	 * the class to describes a grid puzzle item.
	 * 
	 * @author ciprianursu
	 * 
	 */
	private static class GridItem extends ImageView {
		public int itemID;
		public boolean isBlank;
		public boolean isMoving;

		public GridItem(Context c, int id) {
			super(c);
			itemID = id;
		}
	}

	public GridAdapter(Activity activity, int size) {
		gridSize = size;
		items = new GridItem[size * size];
		mActivity = activity;

		for (int i = 0; i < items.length - 1; i++) {
			items[i] = new GridItem(activity, i);
			items[i].setImageResource(mIds[i]);
		}

		items[items.length - 1] = new GridItem(activity, items.length - 1);
		items[items.length - 1].isBlank = true;
		items[items.length - 1].setImageResource(R.drawable.blank);
	}

	private void shuffle() {
		Thread shuffleThread = new Thread(new Runnable() {
			@Override
			public void run() {
				Queue<Integer> queue = new ArrayDeque<Integer>(items.length / 2);
				Random ran = new Random();
				ran.setSeed(System.nanoTime());
				int i = 0;
				while (isShufflingOn()) {
					i++;
					int itemId = ran.nextInt(items.length);

					if (i % 7 == 0)
						ran.setSeed(ran.nextLong() + System.nanoTime() - i
								* ran.nextLong());

					if (queue.contains(itemId)) {
						queue.poll();
						continue;
					}

					if (move(getItemPositionById(itemId)) != NO_DIRECTION) {
						if (queue.size() > items.length / 2)
							queue.poll();

						queue.add(itemId);

						try {
							Thread.sleep(300);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		});

		shuffleThread.start();
	}

	synchronized public void startShuffling() {
		shuffleOn = true;
		shuffle();
	}

	synchronized public void stopShuffling() {
		shuffleOn = false;
	}

	synchronized public boolean isShufflingOn() {
		return shuffleOn;
	}

	public boolean isSolved() {
		int i;
		for (i = 0; i < items.length; i++) {
			if (items[i].itemID != i)
				break;
		}
		if (i == items.length)
			return true;

		return false;
	}

	private int getItemPositionById(int id) {
		for (int i = 0; i < items.length; i++) {
			if (id == items[i].itemID)
				return i;
		}

		return -1;
	}

	/**
	 * moves the item(s).
	 * 
	 * @param itemPos
	 */
	public int move(int itemPos) {
		int direction = getMoveDirection(itemPos);
		int blankPos = getBlankPosition();
		if (blankPos < 0 || itemPos == blankPos)
			return NO_DIRECTION;

		switch (direction) {
		case UP_DIRECTION:
			for (int i = blankPos + gridSize; i <= itemPos; i += gridSize) {
				swapItems(blankPos, i);
				blankPos = i;
			}
			break;

		case DOWN_DIRECTION:
			for (int i = blankPos - gridSize; i >= itemPos; i -= gridSize) {
				swapItems(blankPos, i);
				blankPos = i;
			}
			break;

		case LEFT_DIRECTION:
			for (int i = blankPos + 1; i <= itemPos; i++) {
				swapItems(blankPos, i);
				blankPos = i;
			}
			break;

		case RIGHT_DIRECTION:
			for (int i = blankPos - 1; i >= itemPos; i--) {
				swapItems(blankPos, i);
				blankPos = i;
			}
			break;

		default:
			return NO_DIRECTION;
		}

		this.mActivity.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				GridAdapter.this.notifyDataSetChanged();
			}
		});
		return direction;
	}

	private void swapItems(int item1Pos, int item2Pos) {
		GridItem tmp = items[item2Pos];
		items[item2Pos] = items[item1Pos];
		items[item1Pos] = tmp;
	}

	/**
	 * Checks the moving direction
	 * 
	 * @param itemPos
	 * 
	 * @return LEFT_DIRECTION, RIGHT_DIRECTION, UP_DIRECTION, DOWN_DIRECTION or
	 *         NO_DIRECTION
	 */
	public int getMoveDirection(int itemPos) {
		// find the blank spot
		int blankPos = getBlankPosition();

		if (blankPos == itemPos)
			return NO_DIRECTION;

		if (blankPos >= 0) {
			// check if it is on the same line
			if (getX(blankPos) == getX(itemPos)) {
				if (blankPos < itemPos)
					return UP_DIRECTION;
				else
					return DOWN_DIRECTION;
			} else if (getY(blankPos) == getY(itemPos)) {
				// check if it is on the same column
				if (blankPos < itemPos)
					return LEFT_DIRECTION;
				else
					return RIGHT_DIRECTION;
			}
		}

		return NO_DIRECTION;
	}

	public int getX(int position) {
		return position % gridSize;
	}

	public int getY(int position) {
		return position / gridSize;
	}

	public int getItemWidth() {
		return mActivity.getResources().getDrawable(R.drawable.i1)
				.getMinimumWidth();
	}

	public int getItemHeight() {
		return mActivity.getResources().getDrawable(R.drawable.i1)
				.getMinimumHeight();
	}

	/**
	 * set the moving flag for an item.
	 * 
	 * @param position
	 * @param isMoving
	 */
	public void setItemMoving(int position, boolean isMoving) {
		items[position].isMoving = isMoving;
	}

	/**
	 * check if an item is moving or not
	 * 
	 * @param position
	 * @return
	 */
	public boolean isItemMoving(int position) {
		return items[position].isMoving;
	}

	/**
	 * finds the blank spot
	 * 
	 * @return
	 */
	public int getBlankPosition() {
		for (int i = 0; i < items.length; i++) {
			if (items[i].isBlank)
				return i;
		}

		// this should never happen
		return -1;
	}

	/**
	 * @return the gridSize
	 */
	public int getGridSize() {
		return gridSize;
	}

	@Override
	public int getCount() {
		return items.length;
	}

	@Override
	public Object getItem(int position) {
		return items[position];
	}

	@Override
	public long getItemId(int position) {
		return items[position].itemID;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		items[position].setAdjustViewBounds(true);
		items[position].setScaleType(ImageView.ScaleType.CENTER_INSIDE);

		return items[position];
	}
}
